﻿#region 참조 네임스페이스

using Skying.ComponentModel;
using Skying.Net;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Reflection;

#endregion

namespace Skying.Reflection.Update
{
	/// <summary>
	/// <para><see cref="Skying.Reflection.Update.UpdateManager"/>가 업데이트 파일을 다운로드하기 다운로드 모듈을 제공한다.</para>
	/// </summary>
	class UpdateDownloader
	{
		#region 상수

		/// <summary>
		/// 다운로드 최상위 경로 상수를 나타낸다.
		/// </summary>
		protected static readonly string _DOWNLOAD_DIRECTORY_PATH = string.Format(@"{0}\{1}", Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), UpdateManager._UPDATE_MANAGER_GUID);

		#endregion

		#region 변수

		/// <summary>
		/// 다운로드해야 하는 업데이트의 게이트웨이의 개수를 나타낸다.
		/// </summary>
		private int _gatewaysCount;

		/// <summary>
		/// 다운로드해야 하는 업데이트의 게이트웨이 큐 목록을 나타낸다.
		/// </summary>
		private Queue<UpdateSchemaGateway> _gateways;

		/// <summary>
		/// 다운로드된 모든 업데이트의 게이트웨이 목록을 나타낸다.
		/// </summary>
		private IList<UpdateSchemaGateway> _downloadedGateways;

		/// <summary>
		/// 다운로드 중인 업데이트의 게이트웨이를 나타낸다.
		/// </summary>
		private UpdateSchemaGateway _downloadingUpdateGateway;

		/// <summary>
		/// 업데이트를 다운로드 중인지 여부를 나타낸다.
		/// </summary>
		private bool _downloading;

		#endregion

		#region 프로퍼티

		/// <summary>
		/// 업데이트를 다운로드 중인지 여부를 가져온다.
		/// </summary>
		public bool Downloading
		{
			get
			{
				return this._downloading;
			}
		}

		/// <summary>
		/// 다운로드해야 하는 업데이트의 전체 개수를 가져온다.
		/// </summary>
		public int TotalCount
		{
			get
			{
				return this._gatewaysCount;
			}
		}

		/// <summary>
		/// 다운로드한 업데이트의 개수를 가져온다.
		/// </summary>
		public int DownloadedCount
		{
			get
			{
				return this._downloadedGateways.Count;
			}
		}

		/// <summary>
		/// 전체 다운로드 진행률을 가져온다.
		/// </summary>
		public int TotalDownloadedPercentage
		{
			get
			{
				return (int)(this.DownloadedCount * 100 / this.TotalCount);
			}
		}

		/// <summary>
		/// 다운로드 중인 업데이트 게이트웨이를 가져온다.
		/// </summary>
		public UpdateSchemaGateway DownloadingUpdateGateway
		{
			get
			{
				return this._downloadingUpdateGateway;
			}
		}

		/// <summary>
		/// 업데이트 다운로드 진행률이 변경될 때 실행된다.
		/// </summary>
		public Action<AsyncResultEventArgs<int>> DownloadProgressChanged
		{
			get;
			set;
		}

		/// <summary>
		/// 업데이트 다운로드가 완료되면 실행된다.
		/// </summary>
		public Action<AsyncResultEventArgs<IList<UpdateSchemaGateway>>> DownloadCompleted
		{
			get;
			set;
		}

		/// <summary>
		/// 업데이트 다운로드가 중지되면 실행된다.
		/// </summary>
		public Action<AsyncCompletedEventArgs> DownloadStopped
		{
			get;
			set;
		}

		/// <summary>
		/// 다운로드 중인 업데이트의 진행률이 변경될 때 실행된다.
		/// </summary>
		public Action<AsyncResultEventArgs<DownloadingUpdateEventArgs>> DownloadingUpdateProgressChanged
		{
			get;
			set;
		}

		/// <summary>
		/// 업데이트 다운로드 중 오류가 발생하면 실행된다.
		/// </summary>
		public Action<AsyncResultEventArgs<Exception>> Error
		{
			get;
			set;
		}

		#endregion

		#region 생성자/소멸자

		public UpdateDownloader(IList<UpdateSchemaGateway> gateways)
		{
			this._gateways = new Queue<UpdateSchemaGateway>(gateways);

			this._gatewaysCount = gateways.Count;

			this._downloadedGateways = new List<UpdateSchemaGateway>();
		}

		#endregion

		#region 다운로드

		/// <summary>
		/// 업데이트 다운로드를 시작한다.
		/// </summary>
		public void Start()
		{
			if (this._gateways.Count == 0) return;

			// 다운로드 중임을 표시한다.
			this._downloading = true;

			// 다운로드를 시작한다.
			this.Download(this._gateways.Dequeue());
		}

		/// <summary>
		/// 업데이트를 다운로드한다.
		/// </summary>
		private void Download(UpdateSchemaGateway gateway)
		{
			this._downloadingUpdateGateway = gateway;

			try
			{
				WebClient webClient = new WebClient();

				// 파일 다운로드가 완료된 경우 실행된다.
				webClient.DownloadFileCompleted += (sender, e) =>
				{
					if (e.Error != null)
					{
						Synchronization.InvokeAndCallback(() => { return e.Error; }, this.Error, null);

						return;
					}

					this._downloadedGateways.Add(gateway);

					try
					{
						// DownloadProgressChanged을 실행하여 전체 다운로드 진행률을 알린다.
						Synchronization.InvokeAndCallback(() => { return TotalDownloadedPercentage; }, this.DownloadProgressChanged, null);
					}
					finally
					{
						// 큐에 업데이트 게이트웨이가 남아있고 중지하지 않은 경우이면 다운로드를 계속한다.
						if (this._gateways.Count > 0 && this._downloading)
						{
							this.Download(this._gateways.Dequeue());
						}
						else
						{
							switch (this._downloading)
							{
								// 다운로드 중이었을 경우에는 모두 완료된 것이므로 DownloadCompleted을 실행한다.
								case true:

									Synchronization.InvokeAndCallback(() => { return this._downloadedGateways; }, this.DownloadCompleted, null);

									break;

								// 다운로드를 멈추기 위해서 _downloading 변수가 false로 되어있는 경우 DownloadStopped를 실행한다.
								case false:

									Synchronization.InvokeAndCallback(() => { }, this.DownloadStopped, null);

									break;
							}

							// 다운로드가 끝났음을 표시한다.
							this._downloading = false;
						}
					}
				};

				// 파일 다운로드 진행률이 변경될 때 발생한다.
				webClient.DownloadProgressChanged += (sender, e) =>
				{
					DownloadingUpdateEventArgs eventArg = new DownloadingUpdateEventArgs(this._downloadingUpdateGateway, e.ProgressPercentage);

					Synchronization.InvokeAndCallback(() => { return eventArg; }, this.DownloadingUpdateProgressChanged, null);
				};

				// 업데이트 파일을 다운로드한다.
				webClient.DownloadFileAsync(new Uri(gateway.UpdateUri), GetDownloadPath(gateway));
			}
			catch (Exception error)
			{
				Synchronization.InvokeAndCallback(() => { return error; }, this.Error, null);
			}
		}

		/// <summary>
		/// <para>진행 중인 다운로드를 중단시킨다.</para>
		/// <para>업데이트를 다운로드하는 <see cref="Skying.Net.WebClient"/> 개체가 동작 중일 때만 실행 가능하다.</para>
		/// </summary>
		public void Cancel()
		{
			if (!this._downloading) return;

			this._downloading = true;
		}

		/// <summary>
		/// 업데이트 게이트웨이에 대한 다운로드 경로를 가져온다.
		/// </summary>
		/// <param name="gateway">다운로드할 업데이트의 게이트웨이를 설정한다.</param>
		/// <returns>다운로드할 업데이트의 다운로드 경로가 반환된다.</returns>
		public static string GetDownloadPath(UpdateSchemaGateway gateway)
		{
			// 업데이트 게이트웨이에 대한 업데이트 다운로드 경로를 구성한다.
			string downloadFilePath = string.Format(@"{0}\{1}\{2}.zip", _DOWNLOAD_DIRECTORY_PATH, gateway.Id, gateway.Id);

			string downloadPath = Path.GetDirectoryName(downloadFilePath);

			if (!Directory.Exists(downloadPath))
			{
				Directory.CreateDirectory(downloadPath);
			}

			return downloadFilePath;
		}

		#endregion
	}
}